home *** CD-ROM | disk | FTP | other *** search
- // Copyright (C) 1999-2000 Id Software, Inc.
- //
- // cg_playerstate.c -- this file acts on changes in a new playerState_t
- // With normal play, this will be done after local prediction, but when
- // following another player or playing back a demo, it will be checked
- // when the snapshot transitions like all the other entities
-
- #include "cg_local.h"
-
- /*
- ==============
- CG_CheckAmmo
-
- If the ammo has gone low enough to generate the warning, play a sound
- ==============
- */
- void CG_CheckAmmo( void ) {
- int i;
- int total;
- int previous;
- int weapons;
-
- // see about how many seconds of ammo we have remaining
- weapons = cg.snap->ps.stats[ STAT_WEAPONS ];
- total = 0;
- for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) {
- if ( ! ( weapons & ( 1 << i ) ) ) {
- continue;
- }
- switch ( i ) {
- case WP_ROCKET_LAUNCHER:
- case WP_GRENADE_LAUNCHER:
- case WP_RAILGUN:
- case WP_SHOTGUN:
- total += cg.snap->ps.ammo[i] * 1000;
- break;
- default:
- total += cg.snap->ps.ammo[i] * 200;
- break;
- }
- if ( total >= 5000 ) {
- cg.lowAmmoWarning = 0;
- return;
- }
- }
-
- previous = cg.lowAmmoWarning;
-
- if ( total == 0 ) {
- cg.lowAmmoWarning = 2;
- } else {
- cg.lowAmmoWarning = 1;
- }
-
- // play a sound on transitions
- if ( cg.lowAmmoWarning != previous ) {
- trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND );
- }
- }
-
- /*
- ==============
- CG_DamageFeedback
- ==============
- */
- void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) {
- float left, front, up;
- float kick;
- int health;
- float scale;
- vec3_t dir;
- vec3_t angles;
- float dist;
- float yaw, pitch;
-
- // show the attacking player's head and name in corner
- cg.attackerTime = cg.time;
-
- // the lower on health you are, the greater the view kick will be
- health = cg.snap->ps.stats[STAT_HEALTH];
- if ( health < 40 ) {
- scale = 1;
- } else {
- scale = 40.0 / health;
- }
- kick = damage * scale;
-
- if (kick < 5)
- kick = 5;
- if (kick > 10)
- kick = 10;
-
- // if yaw and pitch are both 255, make the damage always centered (falling, etc)
- if ( yawByte == 255 && pitchByte == 255 ) {
- cg.damageX = 0;
- cg.damageY = 0;
- cg.v_dmg_roll = 0;
- cg.v_dmg_pitch = -kick;
- } else {
- // positional
- pitch = pitchByte / 255.0 * 360;
- yaw = yawByte / 255.0 * 360;
-
- angles[PITCH] = pitch;
- angles[YAW] = yaw;
- angles[ROLL] = 0;
-
- AngleVectors( angles, dir, NULL, NULL );
- VectorSubtract( vec3_origin, dir, dir );
-
- front = DotProduct (dir, cg.refdef.viewaxis[0] );
- left = DotProduct (dir, cg.refdef.viewaxis[1] );
- up = DotProduct (dir, cg.refdef.viewaxis[2] );
-
- dir[0] = front;
- dir[1] = left;
- dir[2] = 0;
- dist = VectorLength( dir );
- if ( dist < 0.1 ) {
- dist = 0.1;
- }
-
- cg.v_dmg_roll = kick * left;
-
- cg.v_dmg_pitch = -kick * front;
-
- if ( front <= 0.1 ) {
- front = 0.1;
- }
- cg.damageX = -left / front;
- cg.damageY = up / dist;
- }
-
- // clamp the position
- if ( cg.damageX > 1.0 ) {
- cg.damageX = 1.0;
- }
- if ( cg.damageX < - 1.0 ) {
- cg.damageX = -1.0;
- }
-
- if ( cg.damageY > 1.0 ) {
- cg.damageY = 1.0;
- }
- if ( cg.damageY < - 1.0 ) {
- cg.damageY = -1.0;
- }
-
- // don't let the screen flashes vary as much
- if ( kick > 10 ) {
- kick = 10;
- }
- cg.damageValue = kick;
- cg.v_dmg_time = cg.time + DAMAGE_TIME;
- cg.damageTime = cg.snap->serverTime;
- }
-
-
-
-
- /*
- ================
- CG_Respawn
-
- A respawn happened this snapshot
- ================
- */
- void CG_Respawn( void ) {
- // no error decay on player movement
- cg.thisFrameTeleport = qtrue;
-
- // display weapons available
- cg.weaponSelectTime = cg.time;
-
- // select the weapon the server says we are using
- cg.weaponSelect = cg.snap->ps.weapon;
- }
-
-
- /*
- ==============
- CG_CheckPlayerstateEvents
-
- ==============
- */
- void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) {
- int i;
- int event;
- centity_t *cent;
-
- if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) {
- cent = &cg_entities[ ps->clientNum ];
- cent->currentState.event = ps->externalEvent;
- cent->currentState.eventParm = ps->externalEventParm;
- CG_EntityEvent( cent, cent->lerpOrigin );
- }
-
- cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ];
-
- for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) {
- if ( ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]
- || i >= ops->eventSequence ) {
- event = ps->events[ i & (MAX_PS_EVENTS-1) ];
-
- cent->currentState.event = event;
- cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ];
- CG_EntityEvent( cent, cent->lerpOrigin );
- }
- }
- }
-
- /*
- ==================
- CG_CheckLocalSounds
- ==================
- */
- void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) {
- int highScore;
-
- // hit changes
- if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) {
- trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND );
- } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) {
- trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND );
- }
-
- // health changes of more than -1 should make pain sounds
- if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) {
- if ( ps->stats[STAT_HEALTH] > 0 ) {
- CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] );
- }
- }
-
-
- // if we are going into the intermission, don't start any voices
- if ( cg.intermissionStarted ) {
- return;
- }
-
- // reward sounds
- if ( ps->persistant[PERS_REWARD_COUNT] > ops->persistant[PERS_REWARD_COUNT] ) {
- switch ( ps->persistant[PERS_REWARD] ) {
- case REWARD_IMPRESSIVE:
- trap_S_StartLocalSound( cgs.media.impressiveSound, CHAN_ANNOUNCER );
- cg.rewardTime = cg.time;
- cg.rewardShader = cgs.media.medalImpressive;
- cg.rewardCount = ps->persistant[PERS_IMPRESSIVE_COUNT];
- break;
- case REWARD_EXCELLENT:
- trap_S_StartLocalSound( cgs.media.excellentSound, CHAN_ANNOUNCER );
- cg.rewardTime = cg.time;
- cg.rewardShader = cgs.media.medalExcellent;
- cg.rewardCount = ps->persistant[PERS_EXCELLENT_COUNT];
- break;
- case REWARD_DENIED:
- trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER );
- break;
- case REWARD_GAUNTLET:
- trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER );
- // if we are the killer and not the killee, show the award
- if ( ps->stats[STAT_HEALTH] ) {
- cg.rewardTime = cg.time;
- cg.rewardShader = cgs.media.medalGauntlet;
- cg.rewardCount = ps->persistant[PERS_GAUNTLET_FRAG_COUNT];
- }
- break;
- default:
- CG_Error( "Bad reward_t" );
- }
- } else {
- // lead changes (only if no reward)
- if ( !cg.warmup ) {
- // never play lead changes during warmup
- if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) {
- if ( cgs.gametype >= GT_TEAM ) {
- if ( ps->persistant[PERS_RANK] == 2 ) {
- trap_S_StartLocalSound( cgs.media.teamsTiedSound, CHAN_ANNOUNCER );
- } else if ( ps->persistant[PERS_RANK] == 0 ) {
- trap_S_StartLocalSound( cgs.media.redLeadsSound, CHAN_ANNOUNCER );
- } else if ( ps->persistant[PERS_RANK] == 1 ) {
- trap_S_StartLocalSound( cgs.media.blueLeadsSound, CHAN_ANNOUNCER );
- }
- } else {
- if ( ps->persistant[PERS_RANK] == 0 ) {
- trap_S_StartLocalSound( cgs.media.takenLeadSound, CHAN_ANNOUNCER );
- } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) {
- trap_S_StartLocalSound( cgs.media.tiedLeadSound, CHAN_ANNOUNCER );
- } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) {
- trap_S_StartLocalSound( cgs.media.lostLeadSound, CHAN_ANNOUNCER );
- }
- }
- }
- }
- }
-
- // timelimit warnings
- if ( cgs.timelimit > 0 ) {
- int msec;
-
- msec = cg.time - cgs.levelStartTime;
-
- if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) {
- cg.timelimitWarnings |= 1;
- trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER );
- }
- if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) {
- cg.timelimitWarnings |= 2;
- trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER );
- }
- if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) {
- cg.timelimitWarnings |= 4;
- trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER );
- }
- }
-
- // fraglimit warnings
- if ( cgs.fraglimit > 0 && cgs.gametype != GT_CTF ) {
- highScore = cgs.scores1;
- if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) {
- cg.fraglimitWarnings |= 1;
- trap_S_StartLocalSound( cgs.media.threeFragSound, CHAN_ANNOUNCER );
- }
- if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) {
- cg.fraglimitWarnings |= 2;
- trap_S_StartLocalSound( cgs.media.twoFragSound, CHAN_ANNOUNCER );
- }
- if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) {
- cg.fraglimitWarnings |= 4;
- trap_S_StartLocalSound( cgs.media.oneFragSound, CHAN_ANNOUNCER );
- }
- }
- }
-
- /*
- ===============
- CG_TransitionPlayerState
-
- ===============
- */
- void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) {
- // check for changing follow mode
- if ( ps->clientNum != ops->clientNum ) {
- cg.thisFrameTeleport = qtrue;
- // make sure we don't get any unwanted transition effects
- *ops = *ps;
- }
-
- // damage events (player is getting wounded)
- if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) {
- CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount );
- }
-
- // respawning
- if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) {
- CG_Respawn();
- }
-
- if ( cg.snap->ps.pm_type != PM_INTERMISSION
- && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) {
- CG_CheckLocalSounds( ps, ops );
- }
-
- // check for going low on ammo
- CG_CheckAmmo();
-
- // run events
- CG_CheckPlayerstateEvents( ps, ops );
-
- // smooth the ducking viewheight change
- if ( ps->viewheight != ops->viewheight ) {
- cg.duckChange = ps->viewheight - ops->viewheight;
- cg.duckTime = cg.time;
- }
- }
-
-